了解 RTMP 协议

学习协议

关于协议的一些延伸和思考

了解 RTMP 协议

RTMP: Real Time Messaging Protocol,是流媒体传输协议,在直播业务中最常用于推流。

由 Adobe 开发,设计的目的:

  1. Flash 播放器和服务器之间传输数据,采用实时数据通信
  2. 低带宽下的高性能传输

这个协议其实不复杂。以前做过直播业务,我不想忘记他,所以在这里记录下我学习的内容。

学习协议

名词定义(3)

在了解 RTMP 的协议内容之前,需要先了解一些名词定义:还可以参考和这位好心人的翻译。看不懂也没事,可以反复回来看。

不过,为了了解 RTMP,一定要知道的就是消息:消息是 RTMP 协议发送的最基本单元,每个消息包含一个或多个音视频帧(要么都是音频、要么都是视频)。消息由块(Chuck)组成。

RTMP 块流(5)

这一节就是讲啥是块流。大概的结构如下图

image

消息格式(5.1)

块是从消息切分出来的,所以无论什么消息,都一定要包含以下的四个字段。

  1. Timestamp:时间戳(4 字节): 消息的时间戳。
  2. Length:长度(3 字节): 消息负载数据的长度。如果消息头不能被省略,那么他的长度也应该包含在长度中。
  3. Type Id:类型 ID(1 字节): 消息的类型。比如 1-6 是保留给协议控制消息,其中 4 是用户控制消息,8 是音频,9 是视频
  4. Message Stream ID: 消息流 ID(4 字节): 这个可以是任意值。使用小字节序。

握手(5.2)

  1. 在这里,我说个题外话:首先,RTMP 基于 TCP,那么一定也会三次握手建立 TCP 连接。包括:客户端发送 SYN、服务器回应 SYN-ACK、客户端发送 ACK,建立可靠的 TCP 连接。
  2. 然后,RTMP 自己也要握手:TCP 连接建立后,RTMP 自己也要握手。这个过程中,发送的是固定大小的握手块(这也属于 RTMP 的消息范畴):
    1. 客户端(Client)发的是:C0/C1/C2
    2. 服务端(Server)回的是:S0/S1/S2

RTMP 的客户端和服务端握完手,就说明连接建立了:

  1. 客户端发送握手请求(C0/C1),其中包含客户端的版本和时间戳。
  2. 服务器必须接收到 C0 或者 C1 消息,才能回应握手请求(S0/S1),包含服务器的版本和时间戳。
  3. 客户端必须收到 S1 消息,才能发 C2 消息。服务器回复一个握手完成消息(S2)。

以上提到了握手块,他们的具体内容是啥呢?

  1. C0/S0: 就是 version,一个字节
    0 1 2 3 4 5 6 7
    +-+-+-+-+-+-+-+-+
    |    version    |
    +-+-+-+-+-+-+-+-+
    C0 and S0 bits
    
  2. C1/S1:
    1. time 时间戳:C1 里面是客户端的时间戳,S1 里面是服务器收到 C1 当时的时间戳。
    2. random 字节
    0                   1                   2                   3
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                        time (4 bytes)                         |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                        zero (4 bytes)                         |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                         random bytes                          |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                         random bytes                          |
    |                           (cont)                              |
    |                             ...                               |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    
  3. C2/S2:
    1. 时间戳 time:C2 里包含的是客户端收到的 S1 包中的时间戳,S2 中的是服务端收到的 C1 包中的时间戳
    2. 时间戳 time2:C2 是客户端收到 S1 这个包的时间点,S2 是服务端收到 C1 这个包的时间点
    3. random 也是同理
      0                   1                   2                   3
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                        time (4 bytes)                         |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                        time2 (4 bytes)                        |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                          random echo                          |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                          random echo                          |
    |                            (cont)                             |
    |                              ...                              |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    

上面那个握手图里,还有一种 ack 消息,用于流量控制。这个 ack 消息会通知对方接收了多少数据(以字节为单位)。服务器和客户端通过 ack 消息确认已接收到的数据,好调整窗口大小和数据发送速率。

块(5.3)

RTMP 协议将一个完整的消息分为多个较小的数据块(Chunks),每个块都有自己的时间戳、序列号和 Chunk Stream ID。这样可以有效地在网络上进行传输和管理,特别是对于大型消息或实时数据流。

块的结构是这样的:块头(基本头+消息头+扩展时间戳)+数据

+--------------+----------------+--------------------+--------------+
| Basic Header | Message Header | Extended Timestamp |  Chunk Data  |
+--------------+----------------+--------------------+--------------+
|                                                    |
|<------------------- Chunk Header ----------------->|

一个块最大 128 个字节(默认的,用 set chuck size,在 Section 5.4.1 会讲,也可以设),如果超过这个长度就分块。

  1. 块头:
    1. Basic Header 基本头:基本头的长度有三种:1,2 或 3 字节,这取决于块流 ID 的长度。基本头包括:
      1. 块流 ID(chunk stream ID )
      2. 块类型(and the chunk type,包含 fmt 字段),这里的块类型(里面的 fmt,2bit)就决定下面 Message Header 的类型。
    2. Message Header:块消息头有 4 种不同的格式,根据块基本头中的"fmt"字段值来选择。比如块类型 0 的消息头占 11 字节;1 类型的占 7 字节;2 类型的 3 字节;3 类型的消息头,消息流 ID、消息长度和时间戳增量都不指定,使用上一个块相同的块流 ID)。关于头的组成:
      • Message Timesatmp: 时间戳
      • Message Length:消息长度(如果这是块的第一个部分)。
      • Message Type ID:消息类型(如音频、视频、控制消息等)。
      • Message Stream ID
    3. Extended Timestamp:如果消息时间戳大于或等于 0xFFFFFF(即 16777215),则使用扩展时间戳字段提供额外的时间信息。
  2. Chunk Data:实际的消息数据部分。
例子(5.3.2)
音频流:消息和分块

比如这个例子,是一个音频流,这个结构就是一个消息

+---------+-----------------+-----------------+-----------------+
|         |Message Stream ID| Message TYpe ID | Time  | Length  |
+---------+-----------------+-----------------+-------+---------+
| Msg # 1 |    12345        |         8       | 1000  |   32    |
+---------+-----------------+-----------------+-------+---------+
| Msg # 2 |    12345        |         8       | 1020  |   32    |
+---------+-----------------+-----------------+-------+---------+
| Msg # 3 |    12345        |         8       | 1040  |   32    |
+---------+-----------------+-----------------+-------+---------+
| Msg # 4 |    12345        |         8       | 1060  |   32    |
+---------+-----------------+-----------------+-------+---------+

对这个音频流的Msg #3分块之后:这里面的 Chunk Stream ID+Chunk Type 就是基本头,Header Data 就是 Message Header

+--------+---------+-----+------------+-----------+------------+
|        | Chunk   |Chunk|Header Data |No.of Bytes|Total No.of |
|        |Stream ID|Type |            |  After    |Bytes in the|
|        |         |     |            |Header     |Chunk       |
+--------+---------+-----+------------+-----------+------------+
|Chunk#1 |    3    |  0  | delta: 1000|   32      |    44      |
|        |         |     | length: 32,|           |            |
|        |         |     | type: 8,   |           |            |
|        |         |     | stream ID: |           |            |
|        |         |     | 12345 (11  |           |            |
|        |         |     | bytes)     |           |            |
+--------+---------+-----+------------+-----------+------------+
|Chunk#2 |    3    |  2  | 20 (3      |   32      |    36      |
|        |         |     | bytes)     |           |            |
+--------+---------+-----+------------+-----------+------------+
|Chunk#3 |    3    |  3  | none (0    |   32      |    33      |
|        |         |     | bytes)     |           |            |
+--------+---------+-----+------------+-----------+------------+
|Chunk#4 |    3    |  3  | none (0    |   32      |    33      |
|        |         |     | bytes)     |           |            |
+--------+---------+-----+------------+-----------+------------+
视频流:消息和分块

这是一个视频流的消息:

+-----------+-------------------+-----------------+-----------------+
|           | Message Stream ID | Message TYpe ID | Time  | Length  |
+-----------+-------------------+-----------------+-----------------+
| Msg # 1   |       12346       |    9 (video)    | 1000  |   307   |
+-----------+-------------------+-----------------+-----------------+

这是组成这个Msg #1的块:

+-------+------+-----+-------------+-----------+------------+
|       |Chunk |Chunk|Header       |No. of     |Total No. of|
|       |Stream| Type|Data         |Bytes after| bytes in   |
|       | ID   |     |             | Header    | the chunk  |
+-------+------+-----+-------------+-----------+------------+
|Chunk#1|  4   |  0  | delta: 1000 |  128      |   140      |
|       |      |     | length: 307 |           |            |
|       |      |     | type: 9,    |           |            |
|       |      |     | stream ID:  |           |            |
|       |      |     | 12346 (11   |           |            |
|       |      |     | bytes)      |           |            |
+-------+------+-----+-------------+-----------+------------+
|Chunk#2|  4   |  3  | none (0     |  128      |   129      |
|       |      |     | bytes)      |           |            |
+-------+------+-----+-------------+-----------+------------+
|Chunk#3|  4   |  3  | none (0     |  51       |   52       |
|       |      |     | bytes)      |           |            |
+-------+------+-----+-------------+-----------+------------+
区分一下 Chunk Stream ID 与 Message Stream ID

我看到这里其实有点晕了。总结一下:Chunk 的 Message Stream ID 与 Message 的 Message Stream ID 来自协议的分层,各自层次的 id 即标识,或者说各自层次的消息归类。

协议控制消息的 ID(5.4)

RTMP 块流,使用 message type IDs: 1, 2, 3, 5, 6 用于协议控制类型的消息

RTMP 消息格式(6)

协议第 6 节

上一节详细讲了块流,这个东西发明的就是为了低带宽传输的。现在这节讲下消息。

RTMP 消息格式(6.1)

格式:消息头+消息负载(数据)

消息头:其实前面 5.1 提到过了

0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Message Type  |                Payload length                 |
|    (1 byte)   |                   (3 bytes)                   |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                           Timestamp                           |
|                           (4 bytes)                           |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                 Stream ID                     |
|                 (3 bytes)                     |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

消息负载:其实就是音频帧、视频帧。比如一个音频帧,多个音频帧,或者一个视频帧,或者多个视频帧。后面讲负载的类型。

用户控制消息 User Control Message(6.2)

前面提到了,message type ID 4 代表用户控制消息。对于所有的用户控制消息,他的 message stream ID 要用 z0,chuck stream ID 要用 2。

对于用户控制消息,他的负载是这样的。这个 Event 在 Section 7.1.7 讲。

+------------------------------+-------------------------
|      Event Type (16 bits)    | Event Data
+------------------------------+-------------------------

RTMP 消息(7.)

这里提到了不同的消息,以及客户端和服务端交互的消息。、

RTMP 消息类型( 7.1)

协议 7.1 节

这个前面提到一点,这里详细说明下消息的类型:

用户控制协议事件(7.1.7)

是前面 6.2 提到的:User Control Message Events

Stream Begin (=0)
Stream EOF (=1)
StreamDry (=2)
SetBuffer Length (=3)
StreamIs Recorded (=4)
PingRequest (=6)
PingResponse (=7)

命令消息(7.2)

客户端和服务端的交互消息通过命令消息完成,使用 AMF 编码(后面会讲)。
发送端会发送一个命令消息,这个消息包括命令名(command name),交互 ID(transaction ID)还有包含相关参数的命令对象(command object)。

命令大体有两种:
NetConnection: 与客户端服务端连接相关的命令。
NetStream: 音视频流在信道传播的时候涉及到的一些命令。

NetConnection(7.2.1)

协议对这几种命令有非常详细的描述,这里列一个例子,connect 命令作用的位置和过程:

+--------------+                              +-------------+
|    Client    |             |                |    Server   |
+------+-------+             |                +------+------+
       |              Handshaking done               |
       |                     |                       |
       |                     |                       |
       |                     |                       |
       |                     |                       |
       |----------- Command Message(connect) ------->|
       |                                             |
       |<------- Window Acknowledgement Size --------|
       |                                             |
       |<----------- Set Peer Bandwidth -------------|
       |                                             |
       |-------- Window Acknowledgement Size ------->|
       |                                             |
       |<------ User Control Message(StreamBegin) ---|
       |                                             |
       |<------------ Command Message ---------------|
       |       (_result- connect response)           |
       |                                             |
NetStream(7.2.2 )

(用 onStatus 事件标记,发送 NetStream 的状态信息)

这里是一个play 命令的例子

     +-------------+                            +------------+
     | Play Client |             |              |   Server   |
     +------+------+             |              +------+-----+
            |        Handshaking and Application       |
            |             connect done                 |
            |                    |                     |
            |                    |                     |
            |                    |                     |
            |                    |                     |
   ---+---- |----- Command Message(createStream) ----->|
Create|     |                                          |
Stream|     |                                          |
   ---+---- |<---------- Command Message --------------|
            |     (_result- createStream response)     |
            |                                          |
   ---+---- |------ Command Message (play) ----------->|
      |     |                                          |
      |     |<------------- SetChunkSize --------------|
      |     |                                          |
      |     |<---- User Control (StreamIsRecorded) ----|
 Play |     |                                          |
      |     |<---- UserControl (StreamBegin) ----------|
      |     |                                          |
      |     |<- Command Message(onStatus-play reset) --|
      |     |                                          |
      |     |<- Command Message(onStatus-play start) --|
      |     |                                          |
      |     |<------------ Audio Message --------------|
      |     |                                          |
      |     |<------------ Video Message --------------|
      |     |                    |                     |
                                 |
        Keep receiving audio and video stream till finishes
消息交换的例子(7.3 )

协议 7.3

协议这里提供了很多消息交换过程的例子。

关于协议的一些延伸和思考

AMF 是个啥

前面在命令消息中提到了 AMF,这个全称是 Action Message Format。指令消息在客户端和服务端之间传递通过 AMF 编码的指令,消息类型 20 代表 AMF0 编码,消息类型 17 代表 AMF3 编码:

分包和组包是怎么个法子

假设有一个帧的大小为 300 字节,按照默认 128 一个块,需要分成 3 个块来传输:

第一个块:

第二个块:

第三个块:

这个其实协议中也举例了,音频流、视频流的分块。

Message_300

Chunk1_128

Chunk2_128

Chunk3_44

关于复用和解复用

大概是这样的:multiplexer 是复用器,其实就是拆包合并成码流,demultiplexer 是解复用器,就是逆过程

OriginalAudioStream

Multiplexer

OriginalVideoStream

audio_chunk2

video_chunk2

audio_chunk1

video_chunk1

Demultiplexer

ReceiveAudioStream

ReceiveVideoStream

播放流程

RTMP 协议规定,播放一个流媒体有两个前提步骤:第一步,建立一个网络连接(NetConnection);第二步,建立一个网络流(NetStream)。其中,网络连接代表服务器端应用程序和客户端之间基础的连通关系。网络流代表了发送多媒体数据的通道。服务器和客户端之间只能建立一个网络连接,但是基于该连接可以创建很多网络流。

播放一个 RTMP 协议的流媒体需要经过以下几个步骤:握手,建立连接,建立流,播放。RTMP 连接都是以握手作为开始的。建立连接阶段用于建立客户端与服务器之间的“网络连接”;建立流阶段用于建立客户端与服务器之间的“网络流”;播放阶段用于传输视音频数据。

如何抓包

比如,在 wireshark 抓包,执行 rtmp 推流,比如筛选 rtmpt 协议。这里面提到的 RTMPT 是 RTMP 扩展协议。
image
image
先看第一个包:
image
fmt 为 00,cs id 为 3,body size: 91,TypeID 0x14=20,是控制协议。

RTMP 的延伸协议

前面提到 RTMPT,这是个延伸协议。延伸协议包括:

关于 RTMP 的鉴权

这个是防止大量推流攻击服务器,以及盗链(比如,我很邪恶,我不是直播商,但是我就想偷直播商或者别人的直播流)。

推流鉴权

IP 白名单推流:

nginx
Copy code
rtmp {
    server {
        listen 1935;

        application live {
            allow 192.168.1.0/24;  # 允许该子网的 IP 推流
            deny all;              # 拒绝其他所有 IP
        }
    }
}

推流鉴权密钥:

nginx
Copy code
rtmp {
    server {
        listen 1935;

        application live {
            push rtmp://backup-server;
            # 可选: 指定推流密钥
            # push rtmp://backup-server?key=your_key;
        }
    }
}

拉流鉴权

示例配置(Nginx RTMP)

URL 鉴权示例:

rtmp {
    server {
        listen 1935;

        application live {
            # Example configuration for token-based access
            on_publish http://localhost:8080/auth;
            on_play http://localhost:8080/auth;
        }
    }
}

关于 RTMP 的时间戳

生成:以毫秒为单位,从连接开始时的零时间起算。

更新:客户端和服务器在生成 RTMP 消息时会为每个消息分配一个时间戳。时间戳值是相对的,基于消息的实际播放时间或发送时间,应该是单调递增的。

计算:时间戳基于消息的播放时间进行计算。RTMP 协议允许消息的时间戳与实际播放时间相匹配,以便正确地同步音频和视频数据。时间戳的增量通常基于媒体数据的传输速率或播放进度。

块传输:每个块都有一个时间戳,这个时间戳可以用来区分和同步块中的数据。即使数据块大小不同,时间戳会帮助保持数据的顺序和时间同步。

同步:时间戳确保音视频同步。

时间戳回绕

RTMP 的时间戳是单调递增的,时间戳到23212^{32}-1之后,时间戳再从 0 开始。

RTMP 设计的时候,为什么要基于 TCP 呢?

一句话总结:TCP 可靠

可靠性:TCP 保证数据包的正确到达,处理丢包、重传和数据完整性,确保流媒体内容准确无误。
顺序性:TCP 确保数据包按照发送顺序到达接收端,避免了数据顺序错乱的问题。
流控制:TCP 具有流控制机制,可以避免数据传输过快导致的接收端缓冲区溢出。

也就是说,RTMP 不需要像那些基于 UDP 的协议(比如 RTP)那样去重点处理丢包冗余重传的过程,收到了就是收到了。另外还有 RTMFP 这种基于 UDP 的协议。

为什么推流都用 RTMP,不用 RTP、HLS、webrtc

  1. 低延迟:
    1. 这个延迟是指:推流到服务器的延迟
    2. 这个延迟比 RTP 肯定比不了,但是相对 HLS 等基于文件的协议,RTMP 的延迟已经算很低,对直播推流很够用了
    3. RTMP 使用的是持续的 TCP 连接,能以较快的速度传输数据流。TCP 保证数据的可靠传输,减少了丢包重传的复杂度。端到端最快可以 1-3s 之内。
  2. 流式传输:RTMP 的基本单元是消息,消息可以包含多个 chuck。数据可以持续发送和播放,不需要等待全部数据接收完毕(HLS,基于 HTTP,基于文件的传输协议)。

那比如 RTP 基于 UDP,为什么 RTP 协议在直播领域应用比 RTMP 少一些,webrtc 也很少

RTP 常用于实时传输,但因为 UDP 不保证传输可靠性,容易丢包,且需要额外的机制来处理重传和同步,增加了复杂性。他这个设计的场景就不是为了直播的。另外,RTMP(以及 HLS)非常可靠,RTP 虽然延时很低但是毕竟用 UDP,不保证 QOS,webrtc 的平台兼容不如 RTMP 和 HLS。所以还是用这两种多。

为什么直播系统不用 RTP 协议

播放用 RTMP 的却很少,为什么?

技术演变:RTMP 逐渐被更现代的协议取代,比如 HLS 和 DASH,这些协议更适应移动设备和 HTTP 基础设施。
设备兼容:现代浏览器和设备对 HLS 支持更广泛,而对 RTMP 的原生支持较少。
网络穿透:HLS 基于 HTTP,能够更好地穿透防火墙和代理,而 RTMP 由于使用了专有端口,可能会遇到阻碍。

RTMP 为什么可能被防火墙阻碍

因为 RTMP 通常使用 TCP 的 1935 端口,而许多防火墙默认阻止或限制不常见的端口和协议。此外,RTMP 的流量可以很难被传统防火墙检测和管理,因此可能会被阻挡以提高安全性。

推流端也可能受防火墙影响,但推流通常在发送端设置了相应的防火墙规则来允许流量通过。总体来说,两端都可能面临防火墙的阻碍,但播放端的问题更为常见。

因为推流端面对的是服务器,服务器可以同意设置规则,但是播放端各有各的规则。

虽然 RTMP 被设计成使用 RTMP 块流传输,但是它也可以使用其他传输协议来发送消息。这里可以用啥协议?

以下几种协议或技术可以用来实现 RTMP 的消息传输:

WebSocket:将 RTMP 流封装在 WebSocket 连接中。WebSocket 允许在客户端和服务器之间建立持久的双向通信通道。

HTTP:将 RTMP 流转换成 HTTP 流媒体传输,例如通过 HTTP-FLV(Flash Video)或 HLS(HTTP Live Streaming)。这也是很多直播选择的方式。

RTSP(Real-Time Streaming Protocol):将流从 RTMP 转发到 RTSP 客户端。这个我没怎么听说过,不太了解。

UDP:这个也是需要转换。

QUIC:Google 开发的传输协议,基于 UDP,目的是减少延迟和提高传输速度。低延时直播会用到这种协议。

RTMP 都已经 TCP 握手了,为啥还要自己握手,直接就开始发不行吗

这个握手是用于建立和维护流媒体传输的会话,就是虽然 TCP 握手完事了,但是 RTMP 自己还是需要一个 session 的管理机制,以防例如以下的 case 出现:

  1. c0s0 用于交换版本,如果版本不匹配,客户端和服务器就不用继续握手。
  2. c1s1 用于同步时间戳,如果差异很大,可能会导致握手失败,因为时间戳用于确保数据流的正确性和同步。
  3. c2 和 s2 包含还随机数据,验证握手过程的完整性。如果这些随机数据不匹配,握手也失败。

所以不是所有的情况都允许你发数据。

总结

RTMP 的特点:

RTMP 协议的基本数据单元是消息(Message),传输的过程中消息会被拆分为更小的消息块(Chunk)单元。通常一个消息(message)包含一个或多个完整的音、视频帧。最后将分割后的消息块通过 TCP 协议传输,接收端再反解,将消息块恢复成流媒体数据。
优点:

  1. 专为流媒体开发的协议,对底层的优化比其它协议更加优秀
  2. Adobe Flash 支持好,基本上所有的编码器(摄像头之类)都支持 RTMP 输出。现在 PC 市场巨大,PC 主要是 Windows,Windows 的浏览器基本上都支持 Flash。
  3. RTMP 适合长时间播放,曾经有过测试,联系 100 万秒,即 10 天多连续播放没有出现问题。
  4. 最后 RTMP 的延迟相对较低,一般延时在 1-3s 之间,一般的视频会议,互动式直播,完全是够用的。

缺点:

  1. 基于 TCP 传输,非公共端口,可能会被防火墙阻拦;
  2. 为 Adobe 私有协议,很多设备无法播放,特别是在 iOS 端,需要使用第三方解码器才能播放。

补充:什么是有状态协议?
ANS:有状态协议就是就通信双方要记住双方,并且共享一些信息。而无状态协议的通信每次都是独立的,与你上一次的通信没什么关系。不记录上下文

References

RTMP 协议规范(中文翻译)
Adobe RTMP Specification

了解 RTMP 协议

学习协议

关于协议的一些延伸和思考